/*
 *  Copyright 1999-2019 Seata.io Group.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.auditlog.datasource.table;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.auditlog.datasource.checker.KeywordChecker;
import com.auditlog.datasource.checker.KeywordCheckerFactory;
import com.auditlog.exception.NotSupportException;
import com.auditlog.util.CollectionUtils;
import com.auditlog.util.ColumnUtils;
import com.auditlog.util.Constants;

import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class TableMeta {

    private String tableName;

    private String remarks;

    /**
     * key: column name
     */
    private final Map<String, ColumnMeta> allColumns = new HashMap<>();

    /**
     * key: index name
     */
    private final Map<String, IndexMeta> allIndexes = new LinkedHashMap<>();

    /**
     * Gets table name.
     *
     * @return the table name
     */
    public String getTableName() {
        return tableName;
    }

    /**
     * Sets table name.
     *
     * @param tableName the table name
     */
    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    /**
     * get table comment
     *
     * @return: java.lang.String
     */
    public String getRemarks() {
        return remarks;
    }

    /**
     * set table comment
     *
     * @param remarks
     * @return: void
     */
    public void setRemarks(String remarks) {
        this.remarks = remarks;
    }

    /**
     * Gets column meta.
     *
     * @param colName the col name
     * @return the column meta
     */
    public ColumnMeta getColumnMeta(String colName) {
        ColumnMeta columnMeta = allColumns.get(colName);
        if (columnMeta == null) {
            String lowerColumnName = colName.toLowerCase(Locale.ROOT);
            columnMeta = allColumns.values().stream().filter(column -> column.getColumnName().toLowerCase(Locale.ROOT).equals(lowerColumnName)).findAny().orElse(null);
        }
        return columnMeta;
    }

    /**
     * Gets all columns.
     *
     * @return the all columns
     */
    public Map<String, ColumnMeta> getAllColumns() {
        return allColumns;
    }

    /**
     * Gets all indexes.
     *
     * @return the all indexes
     */
    public Map<String, IndexMeta> getAllIndexes() {
        return allIndexes;
    }

    /**
     * Gets auto increase column.
     *
     * @return the auto increase column
     */
    public ColumnMeta getAutoIncreaseColumn() {
        for (Entry<String, ColumnMeta> entry : allColumns.entrySet()) {
            ColumnMeta col = entry.getValue();
            if ("YES".equalsIgnoreCase(col.getIsAutoincrement())) {
                return col;
            }
        }
        return null;
    }

    /**
     * 获取带表别名的主键
     *
     * @param alias
     * @return: java.util.List<java.lang.String>
     */
    public List<String> getPkNameListWithAlias(String alias) {
        return this.getPrimaryKeyWithAlias(alias, true);
    }


    /**
     * 按照表中列的顺序获取全部列名
     *
     * @return: java.util.List<java.lang.String>
     */
    public List<String> getColumnNamesByOrder(String alias) {
        return this.getAllColumns().values().stream().sorted(Comparator.comparingInt(ColumnMeta::getOrdinalPosition))
                .map(column -> getColumnNameWithAlias(alias, column, true))
                .collect(Collectors.toList());
    }

    public List<String> getColumnNamesByOrder() {
        return getColumnNamesByOrder("");
    }


    /**
     * Gets primary key map.
     *
     * @return the primary key map
     */
    public Map<String, ColumnMeta> getPrimaryKeyMap() {
        Map<String, ColumnMeta> pk = new LinkedHashMap<>();
        allIndexes.forEach((key, index) -> {
            if (index.getIndextype().value() == IndexType.PRIMARY.value()) {
                List<ColumnMeta> values = index.getValues();
                // 保证顺序
                CollectionUtil.sort(values, Comparator.comparingInt(ColumnMeta::getOrdinalPosition));
                for (ColumnMeta col : index.getValues()) {
                    pk.put(col.getColumnName(), col);
                }
            }
        });

        if (pk.size() < 1) {
            throw new NotSupportException(String.format("%s needs to contain the primary key.", tableName));
        }

        return pk;
    }


    /**
     * 获取唯一约束的map(key:索引名，value:唯一键列名集合)
     *
     * @param alias   表别名
     * @param isUpper 是否转为大写
     * @return: java.util.Map<java.lang.String, java.util.List < java.lang.String>>
     */
    public Map<String, List<String>> getUniqueKeyMap(String alias, boolean isUpper) {
        Map<String, List<String>> unique = new LinkedHashMap<>();
        if (allIndexes.size() == 0) {
            return unique;
        }
        Collection<IndexMeta> values = allIndexes.values();
        CollectionUtil.sort(values, Comparator.comparingInt(IndexMeta::getOrdinalPosition));
        Map<String, IndexMeta> allOrderedIndex = new LinkedHashMap<>();
        for (IndexMeta value : values) {
            allOrderedIndex.put(value.getIndexName(), value);
        }

        allOrderedIndex.forEach((key, index) -> {
            if (index.getIndextype().value() == IndexType.UNIQUE.value()) {
                for (ColumnMeta col : index.getValues()) {
                    unique.compute(index.getIndexName(), (k, columnList) -> {
                        List<String> columns = columnList;
                        if (columns == null) {
                            columns = new ArrayList<>();
                        }
                        String columnName = getColumnNameWithAlias(alias, col, true);
                        columns.add(columnName);
                        return columns;
                    });
                }
            }
        });
        return unique;
    }


    /**
     * 获取唯一约束的map(key:索引名，value:唯一键列名集合)
     *
     * @return: java.util.Map<java.lang.String, java.util.List < java.lang.String>>
     */
    public Map<String, List<String>> geUniqueKeyMap() {
        Map<String, List<String>> unique = new HashMap<>();
        allIndexes.forEach((key, index) -> {
            if (index.getIndextype().value() == IndexType.UNIQUE.value()) {
                for (ColumnMeta col : index.getValues()) {
                    unique.compute(index.getIndexName(), (k, columnList) -> {
                        List<String> columns = columnList;
                        if (columns == null) {
                            columns = new ArrayList<>();
                        }
                        columns.add(col.getColumnName());
                        return columns;
                    });
                }
            }
        });
        return unique;
    }


    /**
     * Gets primary key only name.
     *
     * @return the primary key only name
     */
    @SuppressWarnings("serial")
    public List<String> getPrimaryKeyOnlyName() {
        List<String> list = new ArrayList<>();
        for (Entry<String, ColumnMeta> entry : getPrimaryKeyMap().entrySet()) {
            list.add(entry.getKey());
        }
        return list;
    }

    /**
     * 获取带别名的列名
     *
     * @param alias  表别名
     * @param column 列名
     * @return: java.lang.String
     */
    public String getColumnNameWithAlias(String alias, ColumnMeta column, boolean toUpperCase) {
        return getColumnNameWithAlias(alias, column.getColumnName(), column.getDbType(), toUpperCase);
    }

    /**
     * 判断是否是自增主键
     *
     * @return: boolean
     */
    public boolean isAutoIncreasePk() {
        Map<String, ColumnMeta> primaryKeyMap = this.getPrimaryKeyMap();
        if (primaryKeyMap.size() == 1) {
            for (String key : primaryKeyMap.keySet()) {
                ColumnMeta columnMeta = primaryKeyMap.get(key);
                return columnMeta.isAutoincrement();
            }
        }
        return false;
    }

    public String getColumnNameWithAlias(String alias, String columnName, String dbType, boolean toUpperCase) {
        String prefix = (StrUtil.isNotEmpty(alias) ?
                alias + Constants.DOT : "");
        if (toUpperCase) {
            prefix = prefix.toUpperCase(Locale.ROOT);
        }
        KeywordChecker checker = KeywordCheckerFactory.getChecker(dbType);
        if (checker.check(columnName)) {
            columnName = ColumnUtils.addEscape(columnName, dbType);
        } else {
            if (toUpperCase) {
                columnName = columnName.toUpperCase(Locale.ROOT);
            }
        }
        return prefix + columnName;
    }


    /**
     * 获取带表别名的主键列名集合
     *
     * @param alias   表别名
     * @param isUpper 是否转为大写
     * @return: java.util.List<java.lang.String>
     */
    public List<String> getPrimaryKeyWithAlias(String alias, boolean isUpper) {
        List<String> primaryKeyList = new ArrayList<>();
        allIndexes.forEach((key, index) -> {
            if (index.getIndextype().value() == IndexType.PRIMARY.value()) {
                List<ColumnMeta> values = index.getValues();
                // 排序，保证返回的generateKeys返回的顺序和主键的顺序一致
                CollectionUtil.sort(values, Comparator.comparingInt(ColumnMeta::getOrdinalPosition));
                for (ColumnMeta col : values) {
                    String columnNameWithAlias = getColumnNameWithAlias(alias, col, true);
                    primaryKeyList.add(columnNameWithAlias);
                }
            }
        });
        if (primaryKeyList.size() < 1) {
            throw new NotSupportException(String.format("%s needs to contain the primary key.", tableName));
        }
        return primaryKeyList;
    }

    /**
     * Gets all the on update columns only name.
     *
     * @return all the on update columns only name
     */
    public List<String> getOnUpdateColumnsOnlyName() {
        return allColumns.values().stream().filter(ColumnMeta::isOnUpdate)
                .map(ColumnMeta::getColumnName).collect(Collectors.toList());
    }

    /**
     * Gets add escape pk name.
     *
     * @param dbType the db type
     * @return escape pk name list
     */
    public List<String> getEscapePkNameList(String dbType) {
        return ColumnUtils.addEscape(getPrimaryKeyOnlyName(), dbType);
    }

    /**
     * Contains pk boolean.
     *
     * @param cols the cols
     * @return the boolean
     */
    public boolean containsPK(List<String> cols) {
        if (cols == null) {
            return false;
        }

        List<String> pk = getPrimaryKeyOnlyName();
        if (pk.isEmpty()) {
            return false;
        }


        //at least contain one pk
        if (cols.containsAll(pk)) {
            return true;
        } else {
            return CollectionUtils.toUpperList(cols).containsAll(CollectionUtils.toUpperList(pk));
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof TableMeta)) {
            return false;
        }
        TableMeta tableMeta = (TableMeta) o;
        if (!Objects.equals(tableMeta.tableName, this.tableName)) {
            return false;
        }
        if (!Objects.equals(tableMeta.allColumns, this.allColumns)) {
            return false;
        }
        if (!Objects.equals(tableMeta.allIndexes, this.allIndexes)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = Objects.hashCode(tableName);
        hash += Objects.hashCode(allColumns);
        hash += Objects.hashCode(allIndexes);
        return hash;
    }
}
